﻿using System;
using System.Reflection;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Security.Principal;
using Com.FastSearch.Esp.Search.Http;
using Com.FastSearch.Esp.Search.Query;
using Com.FastSearch.Esp.Search.Result;
using Com.FastSearch.Esp.Search.View;
using Com.FastSearch.SharePoint.BL.SearchManager.Admin;
using Com.FastSearch.SharePoint.Interfaces;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using Com.FastSearch.Utils;
using INavigator=Com.FastSearch.Esp.Search.View.INavigator;

using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;

namespace Com.FastSearch.SharePoint.BL.SearchManager
{
    /// <summary>
    /// ESP Search Manager implementation which performs a search against ESP.<br></br>
    /// There will be one or more search Manager instances for each Web Application running the ESPSearch Web Parts.
    /// One per unique qrservers connection.
    /// <remarks>
    /// The Search Manager caches the search views as instanciating the search view is a time consuming process.<br></br>
    /// <b>Note:</b>If the search view changes it's configuration the web application pool must be recycled or the IIS web application must be restarted!<br></br>
    /// </remarks>
    /// The search manager can either be instanciated by providing a qrserver connections string (used if the default web application setting is overwritten in a web part search group)
    /// or with the default configuration as configured in the Web Application level.<br></br>
    /// Site Settings -> Site Collection Administration -> ESP Query Server Connections
    /// </summary>
    public class ESPSearchManager: IESPSearchManager
    {
        /// <summary>
        /// Search parameter as used to enable/disable synonyms
        /// </summary>
        public const string QTF_SYNONYM_PARAM = "qtf_querysynonyms";
        /// <summary>
        /// Search Navigator parameter as used in ESP to specify the navigators
        /// </summary>
        public const string RPF_NAVIGATION_NAVIGATORS_PARAM = "rpf_navigation:navigators";
        /// <summary>
        /// Navition parameter as used in ESP to specify the current navigation selection
        /// </summary>
        public const string NAVIGATION_PARAM = "navigation";
        /// <summary>
        /// Scope navigator parameter in ESP
        /// </summary>
        public const string SCOPE_NAVIGATION_PARAM = "scopenavigation";

        /// <summary>
        /// QRServer connection key used to persist/retrieve the qrservers connection in the web application persistence store
        /// </summary>
        public const string ESP_CONNECTION_KEY = "ESPConnection";

        /// <summary>
        /// Default esp search view. contains search results of all views...
        /// </summary>
        protected string DEFAULT_VIEW = "espsystemwebcluster";

        /// <summary>
        /// Dictionary caching the search views
        /// </summary>
        protected volatile Dictionary<string, ISearchView> searchViewsDict = new Dictionary<string, ISearchView>();

        /// <summary>
        /// Lock used to synchronize access to <see cref="searchViewsDict"/>.
        /// </summary>
        protected static object searchViewsLock = new object();

        /// <summary>
        /// Search configuration as used by this SearchManager instance
        /// </summary>
        protected NameValueCollection searchConfig;

        /// <summary>
        /// Search Factory as used by this SearchManager instance.
        /// </summary>
        protected ISearchFactory searchFactory;

        /// <summary>
        /// Cache for all search manager instances within an web application.
        /// One instance for each configuration.
        /// </summary>
        protected volatile static Dictionary<string, ESPSearchManager> searchManagerDict = new Dictionary<string, ESPSearchManager>();

        /// <summary>
        /// Lock used to synchronize access to <see cref="searchManagerDict"/>.
        /// </summary>
        protected static object searchManagerLock = new object();

        /// <summary>
        /// Web Application instance. Set and used if the SearchManager uses the web application level qrservers configuration.
        /// </summary>
        private readonly SPWebApplication webApp;


        private IQueryBuilderFactory queryBuilderFactory;

        /// <summary>
        /// Not instanceable as a search manager instance has to be associated with a SPWebApplication (Guid)
        /// </summary>
        private ESPSearchManager()
        {
            InitQueryBuilderFactory();
            
        }

        /// <summary>
        /// Init the query builder factory.
        /// Reads the web application configuration for the config parameter IQueryBuilderFactory which specifies the 
        /// IQueryBuilderFactory to be used.
        /// If it is not configured in the web application it falls back to the default FQLQueryBuilderFactory
        /// 
        /// </summary>
        protected void InitQueryBuilderFactory()
        {            
            try {
                string iquerybuilderfactory = ConfigurationSettings.AppSettings["IQueryBuilderFactory"];
                string iquerybuilderfactoryAssembly = ConfigurationSettings.AppSettings["IQueryBuilderFactoryAssembly"];
                if (!string.IsNullOrEmpty(iquerybuilderfactory)) {
                    Type type;
                    if (!string.IsNullOrEmpty(iquerybuilderfactoryAssembly))
                    {
                        Assembly assem = Assembly.Load(iquerybuilderfactoryAssembly);
                        type = assem.GetType(iquerybuilderfactory);
                    }
                    else
                    {
                        type = Type.GetType(iquerybuilderfactory);
                    }
                    
                    IQueryBuilderFactory queryBuilderFact = Activator.CreateInstance(type) as IQueryBuilderFactory;
                    SimpleFactoryManager<IQueryBuilderFactory>.Factory = queryBuilderFact;
                } 
            } catch (Exception) {}
            if (SimpleFactoryManager<IQueryBuilderFactory>.Factory == null)            
            {
                SimpleFactoryManager<IQueryBuilderFactory>.Factory = new FQLQueryBuilderFactory();
            }
            
        }

        /// <summary>
        /// Create a search manager which uses the web application qrservers connection configuration.
        /// </summary>
        /// <param name="app">Web Application this SearchManager is running in</param>
        protected ESPSearchManager(SPWebApplication app) : this()
        {
            webApp = app;
            searchConfig = GetSearchfactoryConfig(app);
            InitSearchFactory();
        }


        //Singleton...
        /// <summary>
        /// Create asearch manager which uses a custom (search group specific) qrservers connection string.
        /// Earch search group can configure it's specific qrservers connection
        /// </summary>
        /// <param name="espconnection">qrservers connection string</param>
        protected ESPSearchManager(string espconnection): this()
        {
            webApp = null;
            searchConfig = GetSearchfactoryConfigFromString(espconnection);
            InitSearchFactory();
        }

        /// <summary>
        /// Create an instance of the search manager with the provided SearchFactory settings.
        /// </summary>
        /// <param name="searchFactory">ISearchFactory containing the qrserver connection string</param>
        private ESPSearchManager(ISearchFactory searchFactory)
            : this()
        {
            webApp = null;
            this.searchFactory = searchFactory;

        }

        /// <summary>
        /// Init the default SearchFactory from the currently set searchConfiguration.
        /// </summary>
        private void InitSearchFactory()
        {
            searchFactory = Com.FastSearch.Esp.Search.SearchFactory.NewInstance(searchConfig);
        }

        /// <summary>
        /// Method which can be called to notify the search manager of any configuation change of the qrserver connection.
        /// Clears the Search Manager cache which makes sure the configuration is reread the next time the serch manger is used.
        /// </summary>
        /// <param name="app"></param>
        public static void ConfigChanged(SPWebApplication app)
        {
            string appidstring = app.Id.ToString();
            if (searchManagerDict.ContainsKey(appidstring))
            {
                lock (searchManagerLock)
                {
                    if (searchManagerDict.ContainsKey(appidstring))
                    {
                        searchManagerDict.Remove(appidstring);
                    }
                }
            }
        }

        /// <summary>
        /// Return an instance of the Search Manager which uses the default qrservers connection as stored in the web application persistence store.
        /// </summary>
        /// <param name="app"></param>
        /// <returns></returns>
        public static IESPSearchManager GetInstance(SPWebApplication app)
        {
            string appidstring = app.Id.ToString();
            if (!searchManagerDict.ContainsKey(appidstring))
            {
                lock (searchManagerLock)
                {
                    if (!searchManagerDict.ContainsKey(appidstring))
                    {
                        searchManagerDict.Add(appidstring, new ESPSearchManager(app));
                    }
                }
            }

            return searchManagerDict[appidstring];
        }

        /// <summary>
        /// Return an instance of the Search Manager which uses a specific (custom) qrservers connection.
        /// </summary>
        /// <param name="espConnection">qrservers connection string to connect to ESP</param>
        /// <returns></returns>
        public static IESPSearchManager GetInstance(string espConnection)
        {
            if (!searchManagerDict.ContainsKey(espConnection))
            {
                lock (searchManagerLock)
                {
                    if (!searchManagerDict.ContainsKey(espConnection))
                    {
                        searchManagerDict.Add(espConnection, new ESPSearchManager(espConnection));
                    }
                }
            }
            return searchManagerDict[espConnection];
        }

        // Only used for unit testing. DO NOT REMOVE
        /// <summary>
        /// create an instance of the search manager.
        /// <remarks>
        /// This static method is private as it's only used for unit testing.</remarks>
        /// </summary>
        /// <param name="searchFactory">ISearchFactory configuration as used by the unit test</param>
        /// <param name="id">a unique id to identify the search manager in cach</param>
        /// <returns></returns>
        private static IESPSearchManager GetInstance(ISearchFactory searchFactory, string id)
        {
            if (!searchManagerDict.ContainsKey(id))
            {
                lock (searchManagerLock)
                {
                    if (!searchManagerDict.ContainsKey(id))
                    {
                        searchManagerDict.Add(id, new ESPSearchManager(searchFactory));
                    }
                }
            }
            return searchManagerDict[id];
        }

        protected IQueryBuilder QueryBuilder
        {
            get
            {
                if (queryBuilderFactory == null)
                {
                    queryBuilderFactory = SimpleFactoryManager<IQueryBuilderFactory>.Factory;
                }
                return queryBuilderFactory.Create();                
            }

        }

        /// <summary>
        /// Do a search agains ESP with the provided IESPSearchSettings.
        /// If the windowsIdentity parameter is != null then the username information is used to
        /// append the SAM (Security Access Module) search parameters.
        /// </summary>
        /// <param name="searchSettings">The search settings used to perform the search</param>
        /// <param name="windowsIdentity">Windows identity for the current user performing the search</param>
        /// <returns></returns>
        /// 
        virtual public IESPSearchResult Search(IESPSearchSettings searchSettings, WindowsIdentity windowsIdentity)
        {           
            string queryString = QueryBuilder.BuildQueryStringFromTemplate(searchSettings.UserQuery, searchSettings.QueryTemplate);

            IQuery query = new Query(queryString);

            // Add the properties/parameters from the querystore to the search query
            AddSearchParameters(query, searchSettings);

            // Add security filtering
            // If the windowsIdentity is NULL, nothing is added.
            AddSecurityParam(query, windowsIdentity);

            IESPSearchResult result = null;
            
            if (string.IsNullOrEmpty(searchSettings.UserQuery))
            {
                string defaultQuery = GetStringParameter("defaultQuery", query);
                if (string.IsNullOrEmpty(defaultQuery))
                {
                return null;
            }

                query.QueryString = defaultQuery;
            }           

            bool useUnity =  GetBooleanParameter("useUnity", query);
            if (useUnity)
            {
                result = DoUnitySearch(query, searchSettings);
            }
            else
            {

                ISearchView searchView = GetSearchView(searchSettings);
                //IQuery query;

                if (!String.IsNullOrEmpty(searchSettings.GetCustomParameterByName("XRANK")))
                {
                    //DKB 
                    //string _fql = "xrank(xrank(string(\"-asdf\",mode=\"simpleany\"),contactstate:California, boost=200),contactcity:Oxnard,boost=500)";

                    string _fql = BuildXrankFql(searchSettings.UserQuery, searchSettings.WeightedSearchQuery);
                    query = new Query(_fql);
                    query.SetParameter(BaseParameter.HITS, 4000);
                    query.SetParameter(BaseParameter.OFFSET, 0);
                    query.SetParameter(BaseParameter.NAVIGATION, true);
                    query.SetParameter(BaseParameter.SPELL, "suggest");
                }
                else
                {

                    /*queryString = QueryBuilder.BuildQueryStringFromTemplate(searchSettings.UserQuery,
                                                                                   searchSettings.QueryTemplate);

                    query = new Query(queryString);
                     */

                    // Add the properties/parameters from the querystore to the search query
                    AddSearchParameters(query, searchSettings);
                }

                // Add security filtering
                // If the windowsIdentity is NULL, nothing is added.
                AddSecurityParam(query, windowsIdentity);

                if (searchView != null)
                {
                    IQueryResult iqueryresult = searchView.Search(query);
                    result = new ESPSearchResult(iqueryresult, searchSettings);
                }
            }
            return result;
        }
        
        
        /*  --- Original, prior to XRank functionality ---
        public IESPSearchResult Search(IESPSearchSettings searchSettings, WindowsIdentity windowsIdentity)
        {
            if (string.IsNullOrEmpty(searchSettings.UserQuery))
            {
                return null;
            }

            ISearchView searchView = GetSearchView(searchSettings);
            
            string queryString = QueryBuilder.BuildQueryStringFromTemplate(searchSettings.UserQuery, searchSettings.QueryTemplate);

            IQuery query = new Query(queryString);

            // Add the properties/parameters from the querystore to the search query
            AddSearchParameters(query, searchSettings);

            // Add security filtering
            // If the windowsIdentity is NULL, nothing is added.
            AddSecurityParam(query, windowsIdentity);

            IESPSearchResult result = null;
            if (searchView != null)
            {
                IQueryResult iqueryresult = searchView.Search(query);
                result = new ESPSearchResult(iqueryresult, searchSettings);
            }
            return result;
        }
         */

        protected bool GetBooleanParameter(string p, IQuery query)
        {
            string retVal = "false";
            if (query.HasParameter(p))
            {
                retVal = query.GetStringParameter(p);
            }
            return Boolean.Parse(retVal);
        }

        protected string GetStringParameter(string p, IQuery query)
        {
            string retVal = "false";
            if (query.HasParameter(p))
            {
                retVal = query.GetStringParameter(p);
            }
            return retVal;
        }


        protected IESPSearchResult DoUnitySearch(IQuery query, IESPSearchSettings searchSettings)
        {
            string espconnection = searchConfig["Com.FastSearch.Esp.Search.Http.QRServers"];
            HttpSearchEngine engine = searchFactory.GetSearchEngine(new Uri("http://" + espconnection));
            IESPSearchResult result = null;

            if (engine != null)
            {
                IQueryResult iqueryresult = engine.Search(query);
                result = new ESPSearchResult(iqueryresult, searchSettings);
            }
            return result;
        }


        /// <summary>
        /// Add the search parameters from the searchSettings to the query instance used to perform the search
        /// </summary>
        /// <param name="query">IQuery instance to perform the search </param>
        /// <param name="searchSettings">search parameters to be used for the search</param>
        protected static void AddSearchParameters(IQuery query, IESPSearchSettings searchSettings)
        {
            query.SetParameter(BaseParameter.HITS, searchSettings.NumberOfResultsPerPage);
            query.SetParameter(BaseParameter.OFFSET, searchSettings.Start1);

            AddSearchParameterIfNotNull(query, QTF_SYNONYM_PARAM, searchSettings.SynonymsEnabled);

            AddSearchParameterIfNotNull(query, BaseParameter.LEMMATIZE, searchSettings.LemmatizationEnabled);

            AddSearchParameterIfNotNull(query, BaseParameter.SPELL, searchSettings.Spellcheck, true);

            // If a sorting custom sort parameter is available use this. Otherwise use the default one.
            if (!string.IsNullOrEmpty(searchSettings.CustomResultSorting))
            {
                AddSearchParameterIfNotNull(query, BaseParameter.SORT_BY, " " + searchSettings.CustomResultSorting);
            } else {
                AddSearchParameterIfNotNull(query, BaseParameter.SORT_BY, " " + searchSettings.ResultSorting);
            }

            AddSearchParameterIfNotNull(query, RPF_NAVIGATION_NAVIGATORS_PARAM, searchSettings.Navigation);

            AddSearchParameterIfNotNull(query, NAVIGATION_PARAM, searchSettings.NavigationParameter);
            AddSearchParameterIfNotNull(query, SCOPE_NAVIGATION_PARAM, searchSettings.ScopeNavigationParameter);

            AddCustomSearchParameter(query, searchSettings.GetAllCustomParameter());

            // Add all custom static query properties
            List<KeyValuePair<string, string>> customroperties = SplitCustomQueryProperties(searchSettings.StaticCustomQueryProperties);

            foreach (KeyValuePair<string, string> propertyvalue in customroperties)
            {
                AddSearchParameterIfNotNull(query, propertyvalue.Key,
                    SPEncode.UrlEncode(propertyvalue.Value));

            }            

        }
        /// <summary>
        /// Add the CustomSearch parameters to the search
        /// Can be used of the webparts are extended and some special additional (not named parameter in IESPSearchSettings) are used for search
        /// </summary>
        /// <param name="query">IQuery instance to perform the search</param>
        /// <param name="list">list of key value pairs of search parameter to be added</param>
        protected static void AddCustomSearchParameter(IQuery query, List<KeyValuePair<string, string>> list)
        {
            if (list != null)
            {
                foreach (KeyValuePair<string, string> keyvalue in list)
                {
                    AddSearchParameterIfNotNull(query, keyvalue.Key, SPEncode.UrlEncode(keyvalue.Value));
                }
            }            
        }

        /// <summary>
        /// Split the Custom query properties from a string to a list of key value pairs
        /// </summary>
        /// <param name="customQueryProperies">static query string which shall be appende to the search</param>
        /// <returns>list of key value pairs containing the properties (key/value pairs) which shall be added to the query</returns>
        private static List<KeyValuePair<string,string>> SplitCustomQueryProperties(string customQueryProperies)
        {
            List<KeyValuePair<string, string>> l = new System.Collections.Generic.List<KeyValuePair<string, string>>();
            if (customQueryProperies != null)
            {
                string[] propValues = customQueryProperies.Split('&');

                for (int i = 0; i < propValues.Length; i++)
                {
                    string[] propValue = propValues[i].Split('=');
                    if (propValue.Length > 0 && !string.IsNullOrEmpty(propValue[0]))
                    switch(propValue.Length) {
                        case 1:
                            l.Add(new KeyValuePair<string,string>( propValue[0], "" ));
                            break;
                        case 2:
                            l.Add(new KeyValuePair<string, string>(propValue[0], propValue[1]));
                            break;
                    }
                }
            }

            return l;
        }

        /// <summary>
        /// Add a serch parameter of the parameter value is not null.<br></br>
        /// If the parameter value is null the parameter is NOT added.
        /// Possibility to lowercase the parameter value
        ///
        /// </summary>
        /// <param name="query">IQuery instance to add the parameter to</param>
        /// <param name="param">parameter name to be added</param>
        /// <param name="paramvalue">parameter value to be added</param>
        /// <param name="doLowercase">do lowercase of true</param>
        private static void AddSearchParameterIfNotNull(IQuery query, BaseParameter param, string paramvalue, bool doLowercase)
        {
            if (paramvalue != null)
            {
                query.SetParameter(param, doLowercase ? paramvalue.ToLower():paramvalue);
            }
        }

        /// <summary>
        /// Add a serch parameter of the parameter value is not null.<br></br>
        /// If the parameter value is null the parameter is NOT added
        /// </summary>
        /// <param name="query">IQuery instance to add the parameter to</param>
        /// <param name="param">parameter name to be added</param>
        /// <param name="paramvalue">parameter value to be added</param>
        private static void AddSearchParameterIfNotNull(IQuery query, BaseParameter param, string paramvalue)
        {
            AddSearchParameterIfNotNull(query, param, paramvalue, false);
        }

        /// <summary>
        /// Add a search parameter of the parameter value is not null.<br></br>
        /// </summary>
        /// <param name="query">IQuery instance to add the parameter to</param>
        /// <param name="param">parameter name to be added</param>
        /// <param name="paramvalue">parameter value to be added</param>
        private static void AddSearchParameterIfNotNull(IQuery query, string param, string paramvalue)
        {
            if (paramvalue != null)
            {
                query.SetParameter(param, paramvalue);
            }
        }



        /// <summary>
        /// Add security filtering to the query based on the Currently logged in user.
        /// <remarks>
        /// <b>Note:</b>The security id = Username is added in plain text.
        /// The default ldap query supports that : (|(samAccountName={0})(objectSid={1}))
        /// </remarks>
        /// </summary>
        /// <param name="query">ESP Query to add the parameter to</param>
        /// <param name="windowsIdentity">Windows identity of the currently logged in user. If this parameter is null the security filtering is not added</param>
        public void AddSecurityParam(IQuery query, WindowsIdentity windowsIdentity)
        {
            if (windowsIdentity != null)
            {
                string username = RemovDomainFromUserName(windowsIdentity.Name);
                query.SetParameter("qtf_securityfql:uid", username);
                query.SetParameter("qtf_security:uid", username);
                query.SetParameter("rpf_security:uid", username);
            }
        }

        /// <summary>
        /// Remove the domain from the UserName as this is the way SAM works.
        /// See "Security Access Module (SAM)" documentation for more information.
        /// </summary>
        /// <param name="usernameWithdomain">user name including domain in the form of domain\username</param>
        /// <returns>the user name without domain</returns>
        private static string RemovDomainFromUserName(string usernameWithdomain)
        {
            string username = "";
            if (!String.IsNullOrEmpty(usernameWithdomain)) {
                int index = usernameWithdomain.IndexOf("\\") + 1;
                if (usernameWithdomain.Length >= index)
                {
                    username = usernameWithdomain.Substring(index);
                }
            }
            return username;
        }

        /// <summary>
        /// Return the ESP Search Factory Configuration with the espconnection parameter as the qrserver(s) config.
        /// Used in the case of an override of the default web application level configuration.
        /// </summary>
        /// <param name="espconnection">esp qrserver(s) connection string.</param>
        /// <returns>Search Factory Configuration as used by the esp search api to connect to ESP</returns>
        private static NameValueCollection GetSearchfactoryConfigFromString(string espconnection)
        {
            return GetBasicSearchfactoryConfig(espconnection);
        }

        /// <summary>
        /// Return the ESP Search Factory Configuration, where the qrserver connection is configured in the Web Application level.
        /// Site Settings -> Site Collection Administration -> ESP Query Server Connections
        /// </summary>
        /// <param name="app">Web Application instance this search manager is running in</param>
        /// <returns>Search Factory Configuration as used by the esp search api to connect to ESP</returns>
        protected NameValueCollection GetSearchfactoryConfig(SPWebApplication app)
        {
            //return GetBasicSearchfactoryConfig(SPWebApplicationQRServersConfig);
            return GetBasicSearchfactoryConfig(GetSPWebApplicationQRServerconfig(app));

        }

        /// <summary>
        /// The ESP Search Factory Configuration where the Com.FastSearch.Esp.Search.Http.QRServers property is set to the
        /// connection method parameter
        /// </summary>
        /// <param name="connection">esp qrserver(s) connection string</param>
        /// <returns>Search Factory Configuration as used by the esp search api to connect to ESP</returns>
        protected static NameValueCollection GetBasicSearchfactoryConfig(string connection)
        {
            NameValueCollection config = new NameValueCollection();
            config.Add("Com.FastSearch.Esp.Search.Http.RequestMethod", "GET");
            config.Add("Com.FastSearch.Esp.Search.Http.QRServers", connection);
            config.Add("Com.FastSearch.Esp.Search.Http.CertiticateFile", "");
            return config;
        }

        /// <summary>
        /// Return the Search View for the current searchSettings
        /// </summary>
        /// <param name="searchSettings">Search Settings, containing the search parameter to be used to do the search.</param>
        /// <returns>The ISearchView which can be used to perform the search</returns>
        protected ISearchView GetSearchView(IESPSearchSettings searchSettings)
        {
            if (searchSettings.ESPSearchView == null || searchSettings.ESPSearchView.Equals(""))
            {
                searchSettings.ESPSearchView = DEFAULT_VIEW;
            }

            return GetSearchView(searchSettings.ESPSearchView);
        }

        /// <summary>
        /// The Search Factory to be used for the search
        /// </summary>
        protected ISearchFactory SearchFactory
        {
            get
            {
                return searchFactory;
            }
        }

        /// <summary>
        /// Return the ISearchView from a view string.
        /// A new search view is created if none has been created yet.
        /// <remarks>
        /// Because of performance reasons the search views are cached.
        /// </remarks>
        /// </summary>
        /// <param name="viewName">name of the view to retrieve the ISearcView for</param>
        /// <returns></returns>
        private ISearchView GetSearchView(string viewName)
        {
            if (!searchViewsDict.ContainsKey(viewName))
            {
                lock (searchViewsLock)
                {
                    if (!searchViewsDict.ContainsKey(viewName))
                    {
                        ISearchView searchView = SearchFactory.GetSearchView(viewName);
                        searchViewsDict.Add(viewName, searchView);
                    }
                }
            }
            return searchViewsDict[viewName];
        }


        /// <summary>
        /// Return all views as a string collection for the current SearchManager instance
        /// </summary>
        /// <returns></returns>
        public StringCollection GetSearchViews()
        {
            ArrayList l = SearchFactory.GetSearchViewList();
            StringCollection sc = new StringCollection();

            sc.AddRange((string[])l.ToArray(typeof(string)));
            return sc;
        }

        /// <summary>
        /// Return all Sorteable fields/rank profiles for a certain view
        /// </summary>
        /// <param name="viewName">name of the search view</param>
        /// <returns>list of sorteable fields/rank-profiles</returns>
        public List<ISortField> GetSortableFieldsAndRankProfiles(string viewName)
        {
            ISearchView view = GetSearchView(viewName);

            ArrayList l = view.ResultSpecification.RankProfiles;
            ArrayList l2 = view.ResultSpecification.FullsortEnabledFields;

            List<ISortField> resu = new List<ISortField>();

            foreach (IRankProfile rankProfile in l)
            {
                resu.Add(new SortField(rankProfile.Name, true));
            }

            foreach (ISimpleField field in l2)
            {
                resu.Add(new SortField(field.Name, false));
            }

            return resu;
        }

        /// <summary>
        /// Return all navigators for a given view
        /// </summary>
        /// <param name="viewName">name of the view</param>
        /// <returns>Dictionary of navigators</returns>
        public IDictionary<string, IESPNavigatorDefinition> GetNavigatorsForView(string viewName)
        {
            IDictionary<string, IESPNavigatorDefinition> navigators = new Dictionary<string, IESPNavigatorDefinition>();
            try
            {

                ISearchView view = GetSearchView(viewName);
                foreach (INavigator navigator in view.ResultSpecification.Navigators)
                {
                    navigators[navigator.Name] = new ESPNavigatorDefinition(navigator);
                }
            }
            catch (Exception)
            {
                // No good logging possibility could be found yet. So nothing is done here.
            }
            return navigators;
        }

        /*
        /// <inheritdoc/>
        public string SPWebApplicationQRServersConfig
        {
            get
            {
                string qrservers = GetQRServerConfigInternal();
                if (qrservers == null)
                {
                    InitWebApplicationData();
                    qrservers = GetQRServerConfigInternal();
                }
                return qrservers;
            }
            set
            {
                SetQRServersConfigInternal(value);
            }
        }
        */

        public static void SetSPWebApplicationQRServerconfig(string QRServers, SPWebApplication spWeb)
        {
            SetQRServersConfigInternal(QRServers, spWeb);
        }

         public static string GetSPWebApplicationQRServerconfig(SPWebApplication spWeb)
         {
             return GetQRServerConfigInternal(spWeb);
         }        

         protected void SetQRServersConfigInternal(string QRServers )
         {             
             SetQRServersConfigInternal(QRServers, webApp);
         }

        /// <summary>
        /// Persist the qrserver string in the web application persistence store.
        /// A SearchManagerConfigException is thrown if the string isnull or empty
        /// </summary>
        /// <param name="QRServers">QRServers connection</param>
         protected static void SetQRServersConfigInternal(string QRServers, SPWebApplication spWeb)
        {
            try
            {
                //Validate
                if (QRServers.Length == 0)
                    throw new SearchManagerConfigException("QRServers cannot be null or empty!");

                //ESPConnectionData data = GetESPConnectionData();
                ESPConnectionData data = GetESPConnectionData(spWeb);
                Dictionary<string, string> entries = data.Entries;
                //Add, replace  new entry
                if (!entries.ContainsKey(ESP_CONNECTION_KEY))
                {
                    entries.Add(ESP_CONNECTION_KEY, QRServers);
                } else
                {
                    entries.Remove(ESP_CONNECTION_KEY);
                    entries.Add(ESP_CONNECTION_KEY, QRServers);
                }
                data.Update();

                // Notify te manager that the config changed.
                ConfigChanged(spWeb);

            }
            catch (Exception ex)
            {
                throw new SearchManagerConfigException(ex.Message, ex);
            }

        }

        protected string GetQRServerConfigInternal()
        {
            return GetQRServerConfigInternal(webApp);
        }

        /// <summary>
        /// Return the QRServer connection as stored in the web application persistence store.
        /// </summary>
        /// <returns>QRServers connection as stroed in the web application persistence store</returns>
        protected static string GetQRServerConfigInternal(SPWebApplication spweb)
        {
            //SPPersistedObject spPersObject;
            ESPConnectionData data = GetESPConnectionData(spweb);

            if (data != null)
            {
                if (data.Entries.ContainsKey(ESP_CONNECTION_KEY))
                {
                    return data.Entries[ESP_CONNECTION_KEY];
                }
            }
            return null;
        }

        /// <summary>
        /// Initializes the qrserver connection with a default qrserver connection string if none exist for
        /// te current web application.
        /// The default qrserver string is set to localhost:15100
        /// </summary>
        private void InitWebApplicationData()
        {
            try
            {
                //See if the web application has an AppData object yet
                //SPPersistedObject spPersObject;
                ESPConnectionData data = GetESPConnectionData();
                if (data == null)
                {
                    Guid id = Guid.NewGuid();
                    data = new ESPConnectionData(webApp, id);
                    data.Update();
                }

                Dictionary<string, string> entries = data.Entries;
                if (entries == null)
                {
                    data.Entries = new Dictionary<string, string>();
                    // Add initial qrserver connection to localhost:<default port>
                    data.Entries.Add(ESP_CONNECTION_KEY, "localhost:15100");
                    data.Update();
                }
            } catch (Exception ex) {
                throw new SearchManagerConfigException("Could not initialize data", ex);
            }
        }

        private static ESPConnectionData GetESPConnectionData(SPWebApplication spweb)
        {
            return spweb.GetChild<ESPConnectionData>(ESPConnectionData.ESP_CONNECTION_DATA);
        }

        /// <summary>
        /// Return the ESPConnectionData from the sharepoint web application persistence store.
        /// </summary>
        /// <returns>ESPConnectionData which stores the search properties which are configured on a web application level. For now this is only the default qrservers string</returns>
        private ESPConnectionData GetESPConnectionData()
        {
            return GetESPConnectionData(webApp);
        }





        // =============================================================================================
        // New methods for XRank support below
        //
        // =============================================================================================


        //"xrank(xrank(string(\"-asdf\",mode=\"simpleany\"),contactstate:California, boost=200),contactcity:Oxnard,boost=500)";
        //contactstate~/~California~/~200~~~~~contactcity~/~Oxnard~/~500
        private string BuildXrankFql(string baseQuery, string wtSearchNavs)
        {
            string outerFql = string.Format("string(\"{0}\",mode=\"simpleany\")", baseQuery), innerFql = string.Empty;
            bool areMultiplevaluesPresent;
            string[] arrNav = Regex.Split(wtSearchNavs, "~~~~~");
            for (int i = 0; i < arrNav.Length; i++)
            {
                string[] arrEachNav = Regex.Split(arrNav[i], "~/~");

                string fieldName = arrEachNav[0];
                string fieldValue = arrEachNav[1];
                int fieldWt = Convert.ToInt32(arrEachNav[2]);  //weight as assigned by user
                int fieldType = 101;

                areMultiplevaluesPresent = MultipleValuesPresent(fieldName, fieldValue, GetPTypeFromNavType(fieldType));
                //fieldType=101 - String navigator, fieldType=102 - Numeric range avigator, fieldType=103 - Word navigator, fieldType =104 - Date range navigator
                switch (fieldType)
                {
                    case 101:
                        if (areMultiplevaluesPresent)
                        {
                            //Build FQL Or expression
                            string[] multFldValues = Regex.Split(fieldValue, ",");
                            StringBuilder sbORExpression = new StringBuilder("OR(");
                            for (int idx = 0; idx < multFldValues.Length; idx++)
                            {
                                sbORExpression.AppendFormat(" {0}:\"{1}\",", fieldName, multFldValues[idx]);
                            }

                            //Remove last comma
                            sbORExpression.Remove(sbORExpression.Length - 1, 1);
                            sbORExpression.Append(")");
                            innerFql = sbORExpression.ToString();
                        }
                        else
                        {
                            innerFql = String.Format(" {0}:\"{1}\" ", fieldName, fieldValue);
                        }
                        break;
                    case 102:
                        if (areMultiplevaluesPresent)
                        {
                            //Build FQL Or expression
                            string[] multFldValues = Regex.Split(fieldValue, ",");
                            StringBuilder sbORExpression = new StringBuilder("OR(");
                            for (int idx = 0; idx < multFldValues.Length; idx++)
                            {
                                //Remove the [ and ] from fieldValue and split by ";"
                                fieldValue = multFldValues[idx].Remove(0, 1);              //Remove starting [
                                fieldValue = fieldValue.Remove(fieldValue.Length - 1, 1);  //Remove ending ]
                                string[] arrFieldValue = Regex.Split(fieldValue, ";");
                                string range1, range2;


                                if (arrFieldValue[0] == String.Empty)
                                    range1 = "min";
                                else
                                    range1 = Convert.ToString(Decimal.Parse(arrFieldValue[0], NumberStyles.Any));

                                if (arrFieldValue[1] == String.Empty)
                                    range2 = "max";
                                else
                                    range2 = Convert.ToString(Decimal.Parse(arrFieldValue[1], NumberStyles.Any));

                                sbORExpression.AppendFormat(" {0}:range({1},{2},from=\"GT\",to=\"LE\"),", fieldName, range1, range2);
                            }
                            //Remove last comma
                            sbORExpression.Remove(sbORExpression.Length - 1, 1);
                            sbORExpression.Append(")");
                            innerFql = sbORExpression.ToString();
                        }
                        else
                        {
                            //Remove the [ and ] from fieldValue and split by ";"
                            fieldValue = fieldValue.Remove(0, 1);                      //Remove starting [
                            fieldValue = fieldValue.Remove(fieldValue.Length - 1, 1);  //Remove ending ]
                            string[] arrFieldValue = Regex.Split(fieldValue, ";");

                            string range1, range2;
                            if (arrFieldValue[0] == String.Empty)
                                range1 = "min";
                            else
                                range1 = Convert.ToString(Decimal.Parse(arrFieldValue[0], NumberStyles.Any));

                            if (arrFieldValue[1] == String.Empty)
                                range2 = "max";
                            else
                                range2 = Convert.ToString(Decimal.Parse(arrFieldValue[1], NumberStyles.Any));

                            innerFql = String.Format(" {0}:range({1},{2},from=\"GT\",to=\"LE\")", fieldName, range1, range2);
                        }
                        break;
                    case 104:
                        if (areMultiplevaluesPresent)
                        {
                            //Build FQL Or expression
                            string[] multFldValues = Regex.Split(fieldValue, ",");
                            StringBuilder sbORExpression = new StringBuilder("OR(");
                            for (int idx = 0; idx < multFldValues.Length; idx++)
                            {
                                //Remove the [ and ] from fieldValue and split by ";"
                                fieldValue = multFldValues[idx].Remove(0, 1); //Remove starting [
                                fieldValue = fieldValue.Remove(fieldValue.Length - 1, 1); //Remove ending ]
                                string[] arrFieldValue = Regex.Split(fieldValue, ";");
                                string range1, range2;
                                if (arrFieldValue[0] == String.Empty)
                                    range1 = "min";
                                else
                                    range1 = arrFieldValue[0];

                                if (arrFieldValue[1] == String.Empty)
                                    range2 = "max";
                                else
                                    range2 = arrFieldValue[1];

                                sbORExpression.AppendFormat(" {0}:range({1},{2},from=\"GT\",to=\"LE\"),", fieldName, range1, range2);
                            }
                            //Remove last comma
                            sbORExpression.Remove(sbORExpression.Length - 1, 1);
                            sbORExpression.Append(")");
                            innerFql = sbORExpression.ToString();
                        }
                        else
                        {
                            //Remove the [ and ] from fieldValue and split by ";"
                            fieldValue = fieldValue.Remove(0, 1); //Remove starting [
                            fieldValue = fieldValue.Remove(fieldValue.Length - 1, 1); //Remove ending ]
                            string[] arrFieldValue = Regex.Split(fieldValue, ";");
                            string range1, range2;
                            if (arrFieldValue[0] == String.Empty)
                                range1 = "min";
                            else
                                range1 = arrFieldValue[0];

                            if (arrFieldValue[1] == String.Empty)
                                range2 = "max";
                            else
                                range2 = arrFieldValue[1];

                            innerFql = String.Format(" {0}:range({1},{2},from=\"GT\",to=\"LE\")", fieldName, range1, range2);
                        }
                        break;
                }

                outerFql = string.Format("xrank({0},{1},boost={2})", outerFql, innerFql, fieldWt);
            }
            return outerFql;
        }


        public bool MultipleValuesPresent(string fieldName, string fieldValue, int fieldType)
        {
            bool retValue = false;
            //Check for data type
            if (fieldType == 2)
            {
                //'if data type is string, then we need to be careful to not to interpret comma within string.
                //'Hence first replace all characters within quotes by # and then check for comma
                string temp = Regex.Replace(fieldValue, "^'[^\n]'$", "#");
                if (Regex.Split(temp, ",").Length > 1)
                    retValue = true;
            }
            else
            {
                //if data type is int or datetime, simply check for presence of comma
                if (Regex.Split(fieldValue, ",", RegexOptions.None).Length > 1)
                    retValue = true;
            }
            return retValue;
        }

        public int GetPTypeFromNavType(int NavType)
        {
            int pType;
            switch (NavType)
            {
                case 101:
                    pType = 2;
                    break;
                case 102:
                    pType = 1;
                    break;
                case 103:
                    pType = 2;
                    break;
                case 104:
                    pType = 3;
                    break;
                default:
                    pType = 2;
                    break;
            }
            return pType;
        }

    }
}
